/*******************************************************************************
 * PushButton Engine
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the License.html file at the root directory of this SDK.
 ******************************************************************************/
package kgame5.pbe.engine.entity {
	import kgame5.pbe.engine.core.PBObject;
	import kgame5.pbe.engine.debug.Logger;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;

	/**
	 * Default implementation of IEntity.
     * 
     * <p>Please use allocateEntity() to get at instances of Entity; this allows
     * us to pool Entities at a later date if needed and do other tricks. Please
     * program against IEntity, not Entity, to avoid dependencies.</p>
     */
    internal class Entity extends PBObject implements IEntity
    {        
        public function get deferring():Boolean
        {
            return _deferring;
        }
        
        public function set deferring(value:Boolean):void
        {
            if(_deferring == true && value == false)
            {
                // Resolve everything, and everything that that resolution triggers.
                var needReset:Boolean = _deferredComponents.length > 0;
                while(_deferredComponents.length)
                {
                    var pc:PendingComponent = _deferredComponents.shift() as PendingComponent;
                    pc.item.register(this, pc.name);
                }
                
                // Mark deferring as done.
                _deferring = false;
                
                // Fire off the reset.
                if(needReset)
                    doResetComponents();                
            }
            
            _deferring = value;
        }
        
        public function get eventDispatcher():IEventDispatcher
        {
            return _eventDispatcher;
        }
        
        public override function initialize(name:String = null, alias:String = null):void
        {            
            // Pass control up.
            super.initialize(name, alias);

            // Resolve any pending components.
            deferring = false;
        }
        
        
        /**
         * Destroys the Entity by removing all components and unregistering it from
         * the name manager.
         *
         * @see IPBObject.destroy
         */
        public override function destroy():void
        {
            // Give listeners a chance to act before we start destroying stuff.
            if(_eventDispatcher.hasEventListener("EntityDestroyed"))
                _eventDispatcher.dispatchEvent(new Event("EntityDestroyed"));
            
            // Unregister our components.
            for each(var component:IEntityComponent in _components)
            {
                if(component.isRegistered)
                    component.unregister();    
            }
            
            // And remove their references from the dictionary.
            for (var name:String in _components)
                delete _components[name];

            // Get out of the NameManager and other general cleanup stuff.
            super.destroy();
        }
        
       
        
        public function addComponent(component:IEntityComponent, componentName:String):Boolean
        {
            // Add it to the dictionary.
            if (!doAddComponent(component, componentName))
                return false;

            // If we are deferring registration, put it on the list.
            if(deferring)
            {
                var p:PendingComponent = new PendingComponent();
                p.item = component;
                p.name = componentName;
                _deferredComponents.push(p);
                return true;
            }

            // We have to be careful w.r.t. adding components from another component.
            component.register(this, componentName);
            
            // Fire off the reset.
            doResetComponents();
            
            return true;
        }
        
        public function removeComponent(component:IEntityComponent):void
        {
            // Update the dictionary.
            if (!doRemoveComponent(component))
                return;

            // Deal with pending.
            if(component.isRegistered == false)
            {
                // Remove it from the deferred list.
                for(var i:int=0; i<_deferredComponents.length; i++)
                {
                    if((_deferredComponents[i] as PendingComponent).item != component)
                        continue;
                    
                    // TODO: Forcibly call register/unregister to ensure onAdd/onRemove semantics?
                    
                    _deferredComponents.splice(i, 1);
                    break;
                }

                return;
            }
            
            component.unregister();
            
            doResetComponents();
        }
        
        public function lookupComponentByType(componentType:Class):IEntityComponent
        {
            for each(var component:IEntityComponent in _components)
            {
                if (component is componentType)
                    return component;
            }
            
            return null;
        }
        
        public function lookupComponentsByType(componentType:Class):Array
        {
            var list:Array = new Array();
            
            for each(var component:IEntityComponent in _components)
            {
                if (component is componentType)
                    list.push(component);
            }
            
            return list;
        }
        
        public function lookupComponentByName(componentName:String):IEntityComponent
        {
            return _components[componentName];
        }
        
       
        
       
        
        
        private function doAddComponent(component:IEntityComponent, componentName:String):Boolean
        {
            if (componentName == "")
            {
                Logger.warn(this, "AddComponent", "A component name was not specified. This might cause problems later.");
            }
            
            if (component.owner)
            {
                Logger.error(this, "AddComponent", "The component " + componentName + " already has an owner. (" + name + ")");
                return false;
            }
            
            if (_components[componentName])
            {
                Logger.error(this, "AddComponent", "A component with name " + componentName + " already exists on this entity (" + name + ").");
                return false;
            }
            
            component.owner = this;
            _components[componentName] = component;
            return true;
        }
        
        private function doRemoveComponent(component:IEntityComponent):Boolean
        {
            if (component.owner != this)
            {
                Logger.error(this, "AddComponent", "The component " + component.name + " is not owned by this entity. (" + name + ")");
                return false;
            }
            
            if (!_components[component.name])
            {
                Logger.error(this, "AddComponent", "The component " + component.name + " was not found on this entity. (" + name + ")");
                return false;
            }
            
            delete _components[component.name];
            return true;
        }
        
        /**
         * Call reset on all the registered components in this entity.
         */
        private function doResetComponents():void
        {
            var oldDefer:Boolean = _deferring;
            deferring = true;
            for each(var component:IEntityComponent in _components)
            {
                // Skip unregistered entities. 
                if(!component.isRegistered)
                    continue;
                
                // Reset it!
                component.reset();                
            }
            deferring = false;
        }
        
        
        
        private var _deferring:Boolean = true;
        
        protected var _components:Dictionary = new Dictionary();
       
        protected var _deferredComponents:Array = new Array();
        protected var _eventDispatcher:EventDispatcher = new EventDispatcher();
	}
}


